{$R-}  // Turn off Range Checking because of ARRAY[0..0] construct below

unit ImgUtil;

// The new algorithms are 5 to 8 imes faster (dirty but fast) and they
// not need so many memory (if the bitmap very large you have a problem ->
// windows must use the swapfile).
{$WARNINGS OFF}
{$HINTS OFF}

interface

uses   Windows, Graphics,math;

  procedure SpiegelnHorizontal  (Bitmap:TBitmap);
  procedure SpiegelnVertikal    (Bitmap:TBitmap);
  procedure Drehen90Grad        (Bitmap:TBitmap);
  procedure Drehen270Grad       (Bitmap:TBitmap);
  procedure Drehen180Grad       (Bitmap:TBitmap);
  FUNCTION Rotate90(Bitmap:TBitmap):  TBitmap;
  procedure ConvertBitmapToGrayscale (const Bmp: TBitmap);
  function Blend(C1, C2: TColor; W1: Integer): TColor;
  procedure GradFill(DC: HDC; ARect: TRect; ClrTopLeft, ClrBottomRight: TColor; Kind: integer = 1);
  function GetHSV(c:Tcolor):integer;

implementation

USES dialogs,
     Classes,    // Rect
     SysUtils;

TYPE
  EBitmapError = CLASS(Exception);
  TRGBArray    = ARRAY[0..0] OF TRGBTriple;
  pRGBArray    = ^TRGBArray;


procedure ConvertBitmapToGrayscale (const Bmp: TBitmap);
var
  x, y, Gray: Integer;
  Row: PRGBArray;
begin
  Bmp.PixelFormat := pf24Bit;
  for y := 0 to Bmp.Height - 1 do
  begin
    Row := Bmp.ScanLine[y];
    for x := 0 to Bmp.Width - 1 do
    begin
      Gray           := (Row[x].rgbtRed + Row[x].rgbtGreen + Row[x].rgbtBlue) div 3;
      Row[x].rgbtRed := Gray;
      Row[x].rgbtGreen := Gray;
      Row[x].rgbtBlue := Gray;
    end;
  end;
end;

procedure SpiegelnHorizontal(Bitmap:TBitmap);
var i,j,w,n :  INTEGER;
    RowIn :  pRGBArray;
    RowOut:  pRGBArray;
    temp:Tbitmap;
begin
    temp:=Tbitmap.create;

    temp.Width  := Bitmap.Width;
    temp.Height := Bitmap.Height;
    temp.PixelFormat := Bitmap.PixelFormat;    // only pf24bit for now
    n:=bitmap.width;
    for j := 0 to Bitmap.Height-1 do begin
      rowout := temp.Scanline[j];
      rowin := Bitmap.Scanline[j];
      for i := 0 to n-1 do rowout[i] := rowin[n-1-i];
    end;
    bitmap.Assign(temp);
    temp.free;
end;


procedure SpiegelnVertikal(Bitmap : TBitmap);
var j,w :  INTEGER;
    help  :  TBitmap;

begin
    help := TBitmap.Create;
    help.Width       := Bitmap.Width;
    help.Height      := Bitmap.Height;
    help.PixelFormat := Bitmap.PixelFormat;
    w := Bitmap.Width*sizeof(TRGBTriple);
    for j := 0 to Bitmap.Height-1 do move(Bitmap.Scanline[j]^,Help.Scanline[Bitmap.Height - 1 - j]^,w);
    Bitmap.Assign(help);
    help.free;
end;


type THelpRGB = packed record
                   rgb    : TRGBTriple;
                   dummy  : byte;
                end;

procedure Drehen270Grad(Bitmap:TBitmap);
var aStream : TMemorystream;
    header  : TBITMAPINFO;
    dc      : hDC;
    P       : ^THelpRGB;
    x,y,b,h : Integer;
    RowOut:  pRGBArray;

BEGIN
   aStream := TMemoryStream.Create;
   aStream.SetSize(Bitmap.Height*Bitmap.Width * 4);
   with header.bmiHeader do begin
     biSize := SizeOf(TBITMAPINFOHEADER);
     biWidth := Bitmap.Width;
     biHeight := Bitmap.Height;
     biPlanes := 1;
     biBitCount := 32;
     biCompression := 0;
     biSizeimage := aStream.Size;
     biXPelsPerMeter :=1;
     biYPelsPerMeter :=1;
     biClrUsed :=0;
     biClrImportant :=0;
   end;
   dc := GetDC(0);
   P  := aStream.Memory;
   GetDIBits(dc,Bitmap.Handle,0,Bitmap.Height,P,header,dib_RGB_Colors);
   ReleaseDC(0,dc);
   b := bitmap.Height;  // rotate
   h := bitmap.Width;   // rotate
   bitmap.Width := b;
   bitmap.height := h;
   for y := 0 to (h-1) do begin
     rowOut := Bitmap.ScanLine[(h-1)-y];
     P  := aStream.Memory;        // reset pointer
     inc(p,y);
     for x := (b-1) downto 0 do begin
        rowout[x] := p^.rgb;
        inc(p,h);
     end;
   end;
   aStream.Free;
end;


procedure Drehen90Grad(Bitmap:TBitmap);
var aStream : TMemorystream;
    header  : TBITMAPINFO;
    dc      : hDC;
    P       : ^THelpRGB;
    x,y,b,h : Integer;
    RowOut:  pRGBArray;

BEGIN
   aStream := TMemoryStream.Create;
   aStream.SetSize(Bitmap.Height*Bitmap.Width * 4);
   with header.bmiHeader do begin
     biSize := SizeOf(TBITMAPINFOHEADER);
     biWidth := Bitmap.Width;
     biHeight := Bitmap.Height;
     biPlanes := 1;
     biBitCount := 32;
     biCompression := 0;
     biSizeimage := aStream.Size;
     biXPelsPerMeter :=1;
     biYPelsPerMeter :=1;
     biClrUsed :=0;
     biClrImportant :=0;
   end;
   dc := GetDC(0);
   P  := aStream.Memory;
   GetDIBits(dc,Bitmap.Handle,0,Bitmap.Height,P,header,dib_RGB_Colors);
   ReleaseDC(0,dc);
   b := bitmap.Height;  // rotate
   h := bitmap.Width;   // rotate
   bitmap.Width := b;
   bitmap.height := h;
   for y := 0 to (h-1) do begin
     rowOut := Bitmap.ScanLine[y];
     P  := aStream.Memory;        // reset pointer
     inc(p,y);
     for x := 0 to (b-1) do begin
        rowout[x] := p^.rgb;
        inc(p,h);
     end;
   end;
   aStream.Free;
end;


procedure Drehen180Grad(Bitmap:TBitmap);
var i,j     :  INTEGER;
    rowIn :  pRGBArray;
    rowOut:  pRGBArray;
    help  : TBitmap;

begin
   help := TBitmap.Create;
   help.Width  := Bitmap.Width;
   help.Height := Bitmap.Height;
   help.PixelFormat := Bitmap.PixelFormat;    // only pf24bit for now
   FOR  j := 0 TO Bitmap.Height - 1 DO BEGIN
     rowIn  := Bitmap.ScanLine[j];
     rowOut := help.ScanLine[Bitmap.Height - j - 1];
     FOR i := 0 TO Bitmap.Width - 1 DO rowOut[Bitmap.Width - i - 1] := rowIn[i]
   END;
   bitmap.assign(help);
   help.free;
end;


FUNCTION Rotate90(Bitmap:TBitmap):  TBitmap;
VAR i,j     :  INTEGER;
        rowIn :  pRGBArray;
BEGIN
   IF   Bitmap.PixelFormat <> pf24bit then
     exit;

   RESULT := TBitmap.Create;
   RESULT.Width  := Bitmap.Height;
   RESULT.Height := Bitmap.Width;
   RESULT.PixelFormat := Bitmap.PixelFormat;    // only pf24bit for now

   // Out[j, Right - i - 1] = In[i, j]
   FOR  j := 0 TO Bitmap.Height - 1 DO  BEGIN
      rowIn  := Bitmap.ScanLine[j];
      FOR i := 0 TO Bitmap.Width - 1 DO
          pRGBArray(RESULT.ScanLine[Bitmap.Width - i - 1])[j] := rowIn[i]
   END;
END;

function Blend(C1, C2: TColor; W1: Integer): TColor;
var
  W2, A1, A2, D, F, G: Integer;
begin
  if C1 < 0 then C1 := GetSysColor(C1 and $FF);
  if C2 < 0 then C2 := GetSysColor(C2 and $FF);

  if W1 >= 100 then D := 1000
  else D := 100;

  W2 := D - W1;
  F := D div 2;

  A2 := C2 shr 16 * W2;
  A1 := C1 shr 16 * W1;
  G := (A1 + A2 + F) div D and $FF;
  Result := G shl 16;

  A2 := (C2 shr 8 and $FF) * W2;
  A1 := (C1 shr 8 and $FF) * W1;
  G := (A1 + A2 + F) div D and $FF;
  Result := Result or G shl 8;

  A2 := (C2 and $FF) * W2;
  A1 := (C1 and $FF) * W1;
  G := (A1 + A2 + F) div D and $FF;
  Result := Result or G;
end;

const
  GRADIENT_CACHE_SIZE = 16;

type
  PRGBQuad = ^TRGBQuad;
  TRGBQuad = Integer;
  PRGBQuadArray = ^TRGBQuadArray;
  TRGBQuadArray = array [0..0] of TRGBQuad;


var
  GradientCache: array [0..GRADIENT_CACHE_SIZE] of array of TRGBQuad;
  NextCacheIndex: Integer = 0;

function FindGradient(Size: Integer; CL, CR: TRGBQuad): Integer;
begin
  Assert(Size > 0);
  Result := GRADIENT_CACHE_SIZE - 1;
  while Result >= 0 do
  begin
    if (Length(GradientCache[Result]) = Size) and
      (GradientCache[Result][0] = CL) and
      (GradientCache[Result][Length(GradientCache[Result]) - 1] = CR) then Exit;
    Dec(Result);
  end;
end;

function MakeGradient(Size: Integer; CL, CR: TRGBQuad): Integer;
var
  R1, G1, B1: Integer;
  R2, G2, B2: Integer;
  R, G, B: Integer;
  I: Integer;
  Bias: Integer;
begin
  Assert(Size > 0);
  Result := NextCacheIndex;
  Inc(NextCacheIndex);
  if NextCacheIndex >= GRADIENT_CACHE_SIZE then NextCacheIndex := 0;
  R1 := CL and $FF;
  G1 := CL shr 8 and $FF;
  B1 := CL shr 16 and $FF;
  R2 := CR and $FF - R1;
  G2 := CR shr 8 and $FF - G1;
  B2 := CR shr 16 and $FF - B1;
  SetLength(GradientCache[Result], Size);
  Dec(Size);
  Bias := Size div 2;
  if Size > 0 then
    for I := 0 to Size do
    begin
      R := R1 + (R2 * I + Bias) div Size;
      G := G1 + (G2 * I + Bias) div Size;
      B := B1 + (B2 * I + Bias) div Size;
      GradientCache[Result][I] := R + G shl 8 + B shl 16;
    end
  else
  begin
    R := R1 + R2 div 2;
    G := G1 + G2 div 2;
    B := B1 + B2 div 2;
    GradientCache[Result][0] := R + G shl 8 + B shl 16;
  end;
end;

function GetGradient(Size: Integer; CL, CR: TRGBQuad): Integer;
begin
  Result := FindGradient(Size, CL, CR);
  if Result < 0 then Result := MakeGradient(Size, CL, CR);
end;

procedure GradFill(DC: HDC; ARect: TRect; ClrTopLeft, ClrBottomRight: TColor; Kind: integer = 1);
const
//  GRAD_MODE: array [0..1] of DWORD = (GRADIENT_FILL_RECT_H, GRADIENT_FILL_RECT_V);
  W: array [0..1] of Integer = (2, 1);
  H: array [0..1] of Integer = (1, 2);
type
  TriVertex = packed record
    X, Y: Longint;
    R, G, B, A: Word;
  end;
var
  V: array [0..1] of TriVertex;
  GR: GRADIENT_RECT;
  Size, I, Start, Finish: Integer;
  GradIndex: Integer;
  R, CR: TRect;
  Brush: HBRUSH;
begin
  if not RectVisible(DC, ARect) then Exit;

  ClrTopLeft := ColorToRGB(ClrTopLeft);
  ClrBottomRight := ColorToRGB(ClrBottomRight);
    { Have to do it manually if msimg32.dll is not available }
    GetClipBox(DC, CR);

    if Kind = 0 then begin
      Size := ARect.Right - ARect.Left;
      if Size <= 0 then Exit;
      Start := 0; Finish := Size - 1;
      if CR.Left > ARect.Left then Inc(Start, CR.Left - ARect.Left);
      if CR.Right < ARect.Right then Dec(Finish, ARect.Right - CR.Right);
      R := ARect; Inc(R.Left, Start); R.Right := R.Left + 1;
    end else begin
      Size := ARect.Bottom - ARect.Top;
      if Size <= 0 then Exit;
      Start := 0; Finish := Size - 1;
      if CR.Top > ARect.Top then Inc(Start, CR.Top - ARect.Top);
      if CR.Bottom < ARect.Bottom then Dec(Finish, ARect.Bottom - CR.Bottom);
      R := ARect; Inc(R.Top, Start); R.Bottom := R.Top + 1;
    end;

    GradIndex := GetGradient(Size, ClrTopLeft, ClrBottomRight);
    for I := Start to Finish do begin
      Brush := CreateSolidBrush(GradientCache[GradIndex][I]);
      Windows.FillRect(DC, R, Brush);
      OffsetRect(R, Integer(Kind = 0), Integer(Kind = 1));
      DeleteObject(Brush);
    end;
end;

function GetHSV(c:Tcolor):integer;
var
  Delta:  double;
  Min  :  double;
  R,G,B:  integer;
  ss,vv,hh:double;
  H,S,V:Integer;
begin
    R := C and $FF;
    G := C shr 8 and $FF;
    B := C shr 16 and $FF;
    
    Min := MinIntValue( [R, G, B] );
    V   := MaxIntValue( [R, G, B] );
    Delta := V - Min;
    if   V =  0  then ss := 0
    else ss := Delta/V;

    if ss = 0 then hh := 0
    else begin
      if      R = V then hh := 60 * (G - B) / Delta
      else if G = V then hh := 120 + 60 * (B - R) / Delta
      else if B = V then hh := 240 + 60 * (R - G) / Delta;
      if hh < 0 then hh := hh + 360;
    end;
    S := round(ss*255);
    H := round(hh*255/360);
    if (r<160) and (g<160) and (b<160) then s:=200;
    result:=s;
end;

end.


